#pragma once
#include <cstdint>
#include <algorithm>
#include <random>
#include <array>
#include "math.h"
#include "cuda_lib.h"
enum class ShapeType:uint16_t
{
	None,
	Sphere,
	Cube
};
constexpr float floatMax = std::numeric_limits<float>::max();
struct IsectData
{
	float t0{ floatMax }, t1{ floatMax };
	vec3 pHit;
	vec3 nHit;
	bool inside{ false };
};
template<class T>
__DEVICE__ void swap(T& l, T& r)
{
	T temp = l;
	l = r;
	r = temp;
}

__DEVICE__ bool solveQuadratic(float a, float b, float c, float& r0, float& r1)
{
	r0 = 1e5, r1 = -1e5;
	float d = b * b - 4 * a * c;
	if (d < 0) return false;
	else if (d == 0) r0 = r1 = -0.5f * b / a;
	else {
		float q = (b > 0) ? -0.5f * (b + sqrtf(d)) : -0.5f * (b - sqrtf(d));
		r0 = q / a, r1 = c / q;
	}

	if (r0 > r1) swap(r0, r1);

	return true;
}
class ShapeObject
{
public:
	__DEVICE__ ShapeType shapetype()const { return type; }

protected:
	ShapeObject(ShapeType _type) :type(_type) {}
	ShapeType type{ ShapeType::None };

};


class SphereShape :public ShapeObject
{

public:
	SphereShape():ShapeObject(ShapeType::Sphere),color(1, 0, 0) {}
	__DEVICE__ bool intersect(const vec3& rayOrig, const vec3& rayDir, IsectData& isect) const
	{
		vec3 rayOrigc = rayOrig - center;
		float a = dot(rayDir,rayDir);
		float b = 2 * dot(rayDir, rayOrigc);
		float c = dot(rayOrigc, rayOrigc) - radius * radius;

		if (!solveQuadratic(a, b, c, isect.t0, isect.t1)) return false;

		if (isect.t0 < 0) {
			if (isect.t1 < 0) return false;
			else {
				isect.inside = true;
				isect.t0 = 0;
			}
		}

		return true;
	}
	vec3 color;
	float radius{ 1 };
	vec3 center{ 0, 0, -4 };
};


__DEVICE__ bool shape_intersect(const ShapeObject* shape, const vec3& ray_orig, const vec3& ray_dir, IsectData& isect, uint32_t* size = nullptr)
{
	if (shape->shapetype() == ShapeType::Sphere)
	{
		if (size)*size = sizeof(SphereShape);
		auto sphere = static_cast<const SphereShape*>(shape);
		return sphere->intersect(ray_orig, ray_dir, isect);
	}
	return false;
}

__DEVICE__ const ShapeObject* scene_intersect(const uint8_t* shapes, uint32_t shapesCount, const vec3& ray_orig, const vec3& ray_dir, IsectData& isect)
{
	uint32_t shape_top{ 0 };
	for (int i = 0; i < shapesCount; ++i)
	{
		auto obj = (const ShapeObject*)&shapes[shape_top];
		uint32_t size{ 0 };
		if (shape_intersect(obj, ray_orig, ray_dir, isect, &size))
			return obj;
		shape_top += size;
	}

	return nullptr;
}
